(function (global, factory) {
    if (typeof module !== 'undefined' && typeof module.exports === 'object') {
        module.exports = factory
    } else {
        global._ = factory;
    }
}(this, (function () {
    let factory;
    // 集合部分-----------------------------------------
    // each 遍历对象、数组、字符串
    // @param{Object|Array|String} obj
    // @return self
    function each(obj, fn, context) {
        return Maybe.of(obj, true, fn).run(['object', 'array', 'string', 'set', 'map'], () => {
            let entries = null;
            if (isMap(obj) || isSet(obj)) {
                entries = obj.entries();
            } else {
                entries = Object.entries(obj);
            }
            for (let [key, value] of entries) {
                fn.bind(context)(value, key, obj);
            }
            return Maybe.of(obj).join()
        })
    }
    // map 遍历对象、数组，返回fn处理的结果
    // @param{Object|Array} obj
    // @return fn 的返回值
    function map(obj, fn, context) {
        return Maybe.of(obj, true, fn).run(['object', 'array'], () => {
            let result = obj;
            if (isArray(obj)) {
                result = [];
            } else if (isObject(obj)) {
                result = {};
            }
            each(obj, function (value, key, list) {
                result[key] = fn.bind(context)(value, key, list)
            }, context)
            return result;
        })
    }
    // reduce 将值进行处理并累加起来
    function reduce(obj, fn, context) {
        let result = '';
        return Maybe.of(obj, true, fn).run(['object', 'array', 'string'], () => {
            if (isArray(obj)) {
                obj = deepClone(obj) // 引用类型，需要克隆一个，否则会改变原来的对象
                result = obj.shift();
            } else if (isObject(obj)) {
                obj = deepClone(obj) // 引用类型，需要克隆一个，否则会改变原来的对象
                result = obj[Object.keys(obj)[0]];
                delete obj[Object.keys(obj)[0]];
            } else {
                result = obj.charAt(0);
                obj = obj.slice(1);
            }
            each(obj, function (value, key, list) {
                result = fn(result, value, key, list)
            }, context)
            return result;
        })
    }
    // 查找元素，返回元素本身
    function find(obj, fn, context) {
        let result;
        return Maybe.of(obj, true, fn).run(['object', 'array', 'string'], () => {
            for (let [key, value] of Object.entries(obj)) {
                if (fn.bind(context)(value, key, obj)) {
                    result = value;
                    break;
                }
            }
            return result;
        })
    }
    // 返回符合fn返回值的元素
    function filter(obj, fn, context) {
        return Maybe.of(obj, true, fn).run(['object', 'array'], () => {
            let result = obj;
            if (isArray(obj)) {
                result = [];
            } else if (isObject(obj)) {
                result = {};
            }
            each(obj, function (value, key, list) {
                if (fn.bind(context)(value, key, list)) {
                    if (isArray(obj)) {
                        result.push(value)
                    } else if (isObject(obj)) {
                        result[key] = value;
                    }
                }
            }, context)
            return result;
        })
    }
    // 所有元素符合添加返回true
    function every(obj, fn, context) {
        return Maybe.of(obj, true, fn).run(['object', 'array', 'string'], () => {
            let result = true;
            for (let [key, value] of Object.entries(obj)) {
                if (!fn.bind(context)(value, key, obj)) {
                    result = false;
                    break;
                }
            }
            return result;
        })
    }
    // 只要有一个元素符合条件返回true
    function some(obj, fn, context) {
        return Maybe.of(obj, true, fn).run(['object', 'array', 'string'], () => {
            let result = false;
            for (let [key, value] of Object.entries(obj)) {
                if (fn.bind(context)(value, key, obj)) {
                    result = true;
                    break;
                }
            }
            return result;
        })
    }
    function contains(obj, ele) {
        return Maybe.of(obj).run(['object', 'array', 'string'], () => {
            let values = Object.values(obj);
            let result = false;
            for (let value of values) {
                if (isArray(ele) || isObject(ele)) {
                    if (isEqual(value,ele)) {
                        result = true;
                        break;
                    }
                } else {
                    result = values.includes(ele);
                }
            }
            return result;
        })
    }
    function max(obj) {
        return Maybe.of(obj).run(['object', 'array'], () => {
            let values = Object.values(obj);
            let result = Math.max(...values);
            if (isNaN(result) || result == -Infinity) {
                return undefined;
            } else {
                return result;
            }
        })
    }
    function min(obj) {
        return Maybe.of(obj).run(['object', 'array'], () => {
            let values = Object.values(obj);
            let result = Math.min(...values);
            if (isNaN(result) || result == -Infinity || result == Infinity) {
                return undefined;
            } else {
                return result;
            }
        })
    }
    function size(obj) {
        return Maybe.of(obj).run(['object', 'array', 'string'], () => {
            let values = Object.keys(obj);
            return values.length;
        })
    }
    function isEmpty(obj) {
        return Maybe.of(obj).run(['object', 'array', 'string'], () => {
            return size(obj) === 0;
        })
    }
    // 数组------------------------------
    // 取最后元素
    function last(array) {
        return Maybe.of(array).run(['array'], () => {
            return array[array.length - 1];
        })
    }
    // 去重
    function unique(array) {
        return Maybe.of(array).run(['array'], () => {
            return [...new Set(array)];
        })
    }
    // 生成数组 range[0,10]
    function range(...args) {
        let start = 0,
            end = 0,
            step = 1,
            array = [];
        let throwError = function (number) {
            if (!isNumber(number)) {
                throw new TypeError(`${number} is not a valid number`)
            }
        }
        if (size(args) === 1) {
            end = args[0]
            throwError(end);
        } else if (size(args) === 2) {
            [start, end] = args;
            throwError(start);
            throwError(end);

        } else if (size(args) === 3) {
            [start, end, step] = args;
            throwError(start);
            throwError(end);
            throwError(step);

        }
        for (let i = start; i < end; i += step) {
            array.push(i);
        }
        return array;
    }
    // 函数--------------------
    // 偏函数
    function partial(fn, ...args) {
        return Maybe.of(fn).run(['function'], () => {
            return function (...argful) {
                let argIndex = 0;
                for (let i = 0; i < args.length; i++) {
                    if (args[i] === null) {
                        args[i] = argful[argIndex]
                        argIndex++;
                    }
                }
                return fn(...args);
            }
        })
    }
    // 缓存运行后的结果
    function memoize(fn) {
        return Maybe.of(fn).run(['function'], () => {
            let cache = {};
            return function (arg) {
                return cache[arg] || (cache[arg] = fn(arg));
            }
        })
    }
    // 柯里化 将多参函数转成一元函数，记住之前的参数
    function curry(fn) {
        return Maybe.of(fn).run(['function'], () => {
            return function curried(...args) {
                if (args.length !== fn.length) {
                    return function (arg) {
                        return curried(...args.concat(arg));
                    }
                }
                return fn(...args)
            }
        })
    }
    // 延迟运行
    function delay(fn, delay = 0, ...args) {
        return Maybe.of(fn).run(['function'], () => {
            setTimeout(function () {
                fn(...args)
            }, delay)
        })
    }
    // 节流 在delay内重复触发只运行一次
    function throttle(fn, delay = 200) {
        let timer = null;
        return function (...args) {
            clearTimeout(timer)
            timer = setTimeout(function () {
                fn(...args)
            }, delay)
        }
    }
    // 只运行一次
    function once(fn, context) {
        return Maybe.of(fn).run(['function'], () => {
            let isAlready = true;
            return function (...args) {
                if (isAlready) {
                    isAlready = false;
                    return fn.bind(context)(...args);
                }
            }
        })
    }
    // 防抖 固定时间只运行一次
    function debounce(fn, wait = 0) {
        return Maybe.of(fn).run(['function'], () => {
            let isAlready = true;
            return (...args) => {
                if (isAlready) {
                    isAlready = false;
                    fn(...args);
                    setTimeout(() => {
                        isAlready = true;
                    }, wait)
                }
            }
        })
    }
    // 管道函数
    function pipe(...args) {
        return (param, ...params) => {
            each(args, fn => {
                param = fn(param, ...params)
            })
            return param;
        }
    }
    // 用于调试pipe管道调用 pipe(tap(fn))
    function tap(fn) {
        return function (param) {
            fn(param);
            return param;
        }
    }
    // 对象----------------------------
    function hasKey(obj, key) {
        return Maybe.of(obj).run(['object'], () => {
            return obj.hasOwnProperty(key)
        })
    }
    // 判断两个变量是否深度相等
    function isEqual(obj, other) {
        if (checkValueType(obj) === checkValueType(other)) { // 先判断类型
            if (isArray(obj)) { // 数组
                if (size(obj) === size(other)) {
                    let result = true;
                    for (let i = 0; i < obj.length; i++) {
                        if (result) {
                            result = isEqual(obj[i], other[i])
                        } else {
                            break;
                        }
                    }
                    return result;
                } else {
                    return false;
                }
            } else if (isObject(obj)) { // 对象
                if (size(obj) === size(other)) {
                    let result = true;
                    for (let key in obj) {
                        if (result) {
                            result = isEqual(obj[key], other[key])
                        } else {
                            break;
                        }
                    }
                    return result;
                } else {
                    return false;
                }
            } else { // 基本数据类型
                return Object.is(obj, other)
            }
        } else {
            return false;
        }
    }
    function inMatch(obj,properties) {
        if (!isObject(properties)) {return false}
        return Maybe.of(obj).run(['object'], () => {
            let result = true;
            for (let key in properties) {
                let item = properties[key];
                if (isArray(item) || isObject(item)) {
                    result = isEqual(item,obj[key])
                } else {
                    if (!Object.is(obj[key],properties[key])) {
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }) 
    }
    // 实用函数---------------------
    
    // 格式化日期
    function formatDate(date,format) {
        if (!isDate(date)) {
            date = new Date();
        }
        return Maybe.of(format).run(['string'], () => {
            let add0 = val=>val < 10 ? "0"+val : val;
            let yyyy = date.getFullYear();
            let M = date.getMonth()+1;
            let MM = add0(M);
            let d = date.getDate();
            let dd = add0(d);
            let H = date.getHours();
            let HH = add0(H);
            let m = date.getMinutes();
            let mm = add0(m);
            let s = date.getSeconds();
            let ss = add0(s);
            let obj = {
                yyyy,M,MM,d,dd,H,HH,m,mm,s,ss
            }
            each(obj,(value,key)=>{
                let regexp = new RegExp(`${key}{1,2}`,'g')
                format = format.replace(regexp, matchVal=>{
                    if (size(matchVal) == 2) { // == MM HH mm ss
                        return add0(value)
                    } else {
                        return value;
                    }
                })
            })
            return format;
        })  
    }
    // 放弃 _ 的引用。浏览器端
    function noConflict() {
        let handle = factory;
        delete _;
        return handle;
    }
    // 生产随机数
    function random(...args) {
        let [min,max] = args
        return Maybe.of(min).run(['number'], () => {
            if (size(args) === 1) {
                return Math.floor(Math.random() * min)
            } else {
                return Math.floor(Math.random() * (max - min)) + min;
            }
        })  
    }
    // 深拷贝
    function deepClone(obj) {
        let element = null;
        if (isArray(obj)) {
            element = []
        } else if (isObject(obj)) {
            element = {}
        }
        if (isArray(obj) || isObject(obj)) {
            for (let [key, value] of Object.entries(obj)) {
                element[key] = deepClone(value)
            }
        } else if (isDate(obj)) {
            element = new Date(obj)
        } else { // 基本数据类型
            element = obj
            return element;
        }
        return element
    }
    // checkValueType 检查数据类型
    function checkValueType(value) {
        let type = Object.prototype.toString.call(value);
        if (typeof value == 'number' && String(value) === 'NaN') {
            type = 'NaN'
        }
        let map = {
            '[object Null]': 'null',
            '[object Number]': 'number',
            '[object String]': 'string',
            '[object Boolean]': 'boolean',
            '[object Undefined]': 'undefined',
            '[object Object]': 'object',
            '[object Function]': 'function',
            '[object Array]': 'array',
            '[object Symbol]': 'symbol',
            '[object Promise]': 'promise',
            '[object GeneratorFunction]': 'function',
            '[object RegExp]': 'regexp',
            '[object Date]': 'date',
            '[object Set]': 'set',
            '[object Map]': 'map',
            'NaN': 'NaN'
        }
        return map[type];
    }
    function isObject(obj) {
        return checkValueType(obj) === 'object'
    }
    function isPromise(obj) {
        return checkValueType(obj) === 'promise';
    }
    function isArray(obj) {
        return checkValueType(obj) === 'array';
    }
    function isFunction(obj) {
        return checkValueType(obj) === 'function';
    }
    function isMap(obj) {
        return checkValueType(obj) === 'map';
    }
    function isSet(obj) {
        return checkValueType(obj) === 'set';
    }
    function isSymbol(obj) {
        return checkValueType(obj) === 'symbol';
    }
    function isString(obj) {
        return typeof obj === 'string'
    }
    function isNumber(obj) {
        return checkValueType(obj) === 'number';
    }
    function isNull(obj) {
        return checkValueType(obj) === 'null';
    }
    function isUndefined(obj) {
        return checkValueType(obj) === 'undefined';
    }
    function isNaN(obj) {
        return checkValueType(obj) === 'NaN'
    }
    function isDate(obj) {
        return checkValueType(obj) === 'date'
    }
    function isRegExp(obj) {
        return checkValueType(obj) === 'regexp'
    }
    // 函子 参数类型处理
    class Maybe {
        // @param{*} value
        // @param{Boolean}fnBool 是否必须传函数
        // @param{Function}fn 当fnBool 为true , 这个是必须
        constructor(value, fnBool = false, fn) {
            this.value = value;
            this.fn = fn;
            this.fnBool = fnBool;
        }
        static of(value, fnBool, fn) {
            return new Maybe(value, fnBool, fn)
        }
        isInvalidValue() {
            let value = this.value;
            if (Object.is(value, NaN) || Object.is(value, undefined) || Object.is(value, null)) {
                return true
            }
            return false;
        }
        join() {
            return this.value
        }
        // @param{Array}typeList 允许的数据类型
        // @param{Function}fn 运行的函数
        run(typeList, fn) {
            let type = checkValueType(this.value);
            if (this.fnBool) { // 必须传函数才能运行
                if (typeof this.fn !== 'function') {
                    throw TypeError('Function is required')
                }
            }
            if (typeList.includes(type)) { // 符合指定的数据类型才运行
                return fn()
            } else {
                // console.error(`${this.value} is not require variable type`)
                return this.value;
            }
        }
    }
    factory = {
        each,
        map,
        reduce,
        find,
        isObject,
        isArray,
        isString,
        filter,
        every,
        some,
        contains,
        max,
        min,
        size,
        last,
        unique,
        range,
        isEmpty,
        partial,
        memoize,
        curry,
        delay,
        throttle,
        once,
        debounce,
        pipe,
        tap,
        hasKey,
        isEqual,
        inMatch,
        random,
        formatDate,
        noConflict,
        deepClone,
        checkValueType,
        isObject,
        isArray,
        isFunction,
        isMap,
        isSet,
        isSymbol,
        isString,
        isNumber,
        isNull,
        isUndefined,
        isNaN,
        isDate,
        isPromise,
        isRegExp,
    }
    return factory;
}())))